Что такое V8 hidden classes и inline caching и как они влияют на производительность?
Hidden Classes
JavaScript — динамически типизированный язык, и в объекте в любой момент можно добавить или удалить свойство. Если бы V8 хранил свойства объектов в обычной hash-map, каждое обращение obj.x требовало бы поиска по строке ключа, что медленно.
Чтобы избежать этого, V8 создаёт для объектов hidden classes (в других движках их называют "shapes" или "maps"). Это скрытая структура, которая описывает набор свойств объекта и смещение каждого свойства в памяти. Объекты с одинаковой формой (одинаковым набором свойств, добавленных в одинаковом порядке) используют один и тот же hidden class.
Переходы между классами
Когда в объект добавляется новое свойство, V8 не переписывает существующий hidden class, а создаёт переход к новому hidden class и связывает его со старым. Так строится дерево переходов.
function Point(x, y) {
this.x = x; // создаётся hidden class C0 -> C1 (добавлено x)
this.y = y; // переход C1 -> C2 (добавлено y)
}
const p1 = new Point(1, 2); // проходит по цепочке C0 -> C1 -> C2
const p2 = new Point(3, 4); // проходит по той же цепочке, разделяет C2 с p1
Если же свойства добавлять в разном порядке или динамически удалять их через delete, объекты получат разные hidden classes даже с одинаковым набором ключей, и оптимизации ниже перестанут работать.
function PointBad(x, y) {
this.x = x;
}
const a = new PointBad(1, 2);
a.y = 2; // добавлено после конструктора — другая цепочка переходов
const b = new PointBad(3, 4);
b.z = 5; // ещё один другой набор свойств
// a и b теперь имеют разные hidden classes,
// хотя выглядят структурно похоже
Inline Caching
Hidden classes дают V8 возможность запомнить конкретное смещение свойства для конкретного места в коде — это и есть inline caching (IC). При первом обращении к obj.x V8 фиксирует, какой hidden class встретился и на каком смещении лежит x. При повторных вызовах того же кода, если hidden class объекта совпадает с закешированным, движок сразу читает значение по смещению, минуя полный поиск свойства.
Состояния IC
- Monomorphic — в данном месте кода всегда встречается один и тот же hidden class. Самый быстрый случай, JIT-компилятор (TurboFan) может максимально специализировать код.
- Polymorphic — встречается несколько (обычно до 4) разных hidden classes. V8 хранит небольшой список вариантов, доступ чуть медленнее.
- Megamorphic — вариантов больше порога. V8 переходит к общему медленному пути поиска свойств, оптимизации теряются, а код может быть деоптимизирован.
function getX(obj) {
return obj.x; // сайт вызова, за которым следит IC
}
getX({ x: 1 }); // hidden class A
getX({ x: 2 }); // тот же класс A — остаётся monomorphic
getX({ x: 3, y: 4 }); // другой hidden class B — становится polymorphic
Практические выводы
- Инициализировать все свойства объекта в конструкторе и в одном и том же порядке.
- Не добавлять и не удалять свойства после создания объекта, использовать
null/undefinedкак значение-заглушку вместоdelete. - Избегать функций, которые принимают объекты разной формы и внутри обращаются к одинаковым полям — это делает сайты вызова polymorphic или megamorphic.
- Использовать классы (
class) или фабричные функции с фиксированной структурой вместо ручного динамического конструирования объектов.
Эти правила не нужно соблюдать фанатично для каждой строки кода, но они критичны в горячих путях — циклах, часто вызываемых функциях, обработчиках событий с высокой частотой.
Что хочет услышать интервьюер
Понимание, что JS-объекты динамические, но V8 оптимизирует доступ через скрытые структуры-классы
Объяснение, как переход между hidden classes возникает при добавлении/удалении свойств в разном порядке
Знание уровней inline caching: monomorphic, polymorphic, megamorphic и их влияние на скорость
Практические рекомендации: инициализировать все поля в конструкторе, не менять форму объекта после создания
Понимание связи с деоптимизацией и тем, почему её стоит избегать в горячем коде
Пример: Одинаковый порядок инициализации сохраняет hidden class
function Point(x, y) {
this.x = x;
this.y = y;
}
const p1 = new Point(1, 2);
const p2 = new Point(3, 4);
// p1 и p2 разделяют один hidden class — доступ к x/y быстрый
Пример: Разный порядок свойств ломает оптимизацию
const a = {};
a.x = 1;
a.y = 2;
const b = {};
b.y = 2; // другой порядок добавления
b.x = 1;
// a и b получают разные hidden classes,
// хотя структурно объекты одинаковые
Пример: Polymorphic сайт вызова
function readX(obj) {
return obj.x; // единая точка, за которой следит inline cache
}
readX({ x: 1 });
readX({ x: 2 });
readX({ x: 3, y: 4 }); // новый hidden class -> polymorphic IC
Типичные ошибки
Путают hidden classes с прототипами или классами ES6 — это разные механизмы
Считают, что hidden classes влияют на любой код одинаково, не различая холодные и горячие пути выполнения
Не могут объяснить разницу между monomorphic и polymorphic состоянием сайта вызова
Не знают, что удаление свойства через delete или добавление свойств в разном порядке создаёт новый hidden class
Дают только теоретический ответ без практических советов по написанию оптимизируемого кода


